Skip to content

perf(actionbars): skip the button build for bars that can never be shown - #2005

Merged
EllesmereGaming merged 1 commit into
EllesmereGaming:mainfrom
JuJuFX-dev:perf/actionbars-hidden-bars
Sep 7, 2026
Merged

perf(actionbars): skip the button build for bars that can never be shown#2005
EllesmereGaming merged 1 commit into
EllesmereGaming:mainfrom
JuJuFX-dev:perf/actionbars-hidden-bars

Conversation

@JuJuFX-dev

Copy link
Copy Markdown
Contributor

What does this PR do?

Action bars that can never be shown -- Visibility set to Hidden, or the bar disabled -- no longer build their twelve action buttons at load. On a profile that hides four of the twelve bars this cuts total UI load time from 164.9 ms to 124.3 ms, and ActionBars' own share of it from 102.3 ms to 62.8 ms. On the default profile it saves about 16.5 ms for every user with no configuration at all, because Bar 9 and Bar 10 ship hidden.

The bar frame is still created, so nothing that looks up a bar frame changes; only the buttons are deferred. They are built the moment the bar leaves the hidden state, whether that comes from the Visibility dropdown, the Toggle Action Bar keybind, Show During Drag or Show When Spellbook Is Open, and the bar arrives complete with its keybinds and styling. Nothing about how a hidden or a visible bar behaves changes for the player.

Three profiling passes established that there is nothing cheaper available here. Per-bar setup is 95 % button creation, and 73 % of that is Blizzard's CreateFrame on the action button template. All the remaining per-button work across all 120 buttons -- the visual refresh, the attribute writes, the flyout registration and the childupdate snippets -- adds up to 7.2 ms, so building fewer buttons is the only lever with real size behind it.

Two supporting changes come with it. ns.IsNeverBar is now the single predicate for "this bar can never become visible", read by both RecomputeNeverBars and the new load-time skip, so the two cannot drift apart. And LayoutBar's memo stamp gained the actual button count: its own comment states that every input the body reads folds into the stamp, the body reads buttons[i], and deferring the build made that count variable at runtime for the first time.

How was it tested?

Tested in-game on live. Revealing a hidden bar from the Visibility dropdown brings it back immediately with all twelve buttons positioned, styled and bound, with no reload needed. Turning on Show During Drag for a bar still set to Hidden and then picking up a spell surfaces that bar with its buttons, and the drop lands on it. The load-time figures above are reload-to-reload measurements on the same character and profile, taken in one sitting.

Checklist

  • New settings default OFF (no behavior change without opt-in) -- N/A, this PR adds no settings; it changes when existing bars build their buttons, not what any option does
  • Zero cost while disabled: no events, hooks, timers or frames are added anywhere; the change removes work from the load path
  • Cheap while enabled: event-driven, no polling and no per-frame allocations. The build runs once per bar at the moment it is revealed, and the only recurring cost is one table lookup on the visibility refresh path, which stops matching once every hidden bar has been revealed
  • No writes onto Blizzard-owned frames (weak-table pattern used); HookScript/hooksecurefunc only, never SetScript on Blizzard frames -- the deferred buttons are the addon's own EABButtons built through the existing helper, and the stance and pet bars, which reuse Blizzard's buttons, are excluded from the change outright
  • Tested in-game on live; no version gates or pre-Midnight APIs added

A bar marked alwaysHidden (or disabled) still built all twelve of its secure
buttons at load. Measured on a reload: 31 ms on a profile hiding four bars, and
16.5 ms on the default profile, where Bar9 and Bar10 ship hidden.

Three readouts placed the cost. Per-bar setup is 95 % button creation, and 73 %
of that is Blizzard's CreateFrame on the action button template. Nothing inside
the loop is reducible - ForceButtonRefresh, the per-button GetCVarBool, the
flyout registration and the childupdate snippets total 7.2 ms across 120
buttons - so the only lever is building fewer buttons.

SetupBar now creates the bar frame and skips the button loop for a bar in the
Never set, leaving barFrames[key] non-nil and barButtons[key] an empty table.
The button half moved to ns.BuildBarButtons so the reveal edge can run it
against the existing frame. RefreshRuntimeVisibility builds them the moment a
bar leaves the Never set, re-runs the secure ref pass and the keybind rebuild,
and defers the cosmetic apply by a frame.

ns.IsNeverBar is now the single predicate for "can never be shown", read by both
RecomputeNeverBars and the load-time skip, so the two cannot drift apart.

LayoutBar's memo gained the actual button count. Its own comment claims every
input the body reads folds into the stamp; the body reads buttons[i], and this
change made that count variable at runtime, so a revealed bar hit an unchanged
stamp and its buttons were never positioned.

Load time on this profile: 164.9 ms -> 124.3 ms, ActionBars OnEnable 102.3 ->
62.8 ms.
@EllesmereGaming
EllesmereGaming merged commit 96303d3 into EllesmereGaming:main Sep 7, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants